package de.hsb.ai.learning;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.hsb.ai.learning.Replay.Listener;
import de.hsb.ai.util.ScotlandJadeGameLogger;
import de.hsb.ai.util.ScotlandJadeGameLogger.Entry;
import de.hsb.ai.util.ScotlandJadeHelper;
import de.hsb.ai.util.map.ScotlandJadeMapHelper;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeVertex;

public class DistanceAnalyzer implements Listener {
	
	public static final Logger logger = LoggerFactory.getLogger("sy.dist");

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String fugitive = null;
		if (args.length >= 1) fugitive = args[0];
		
		int numDetectives = 0;
		if (args.length >= 2) numDetectives = Integer.parseInt(args[1]);
		
		double diameter = ScotlandJadeMapHelper.info.getDiameter();
		logger.debug("diameter: " + diameter);
		
		List<List<List<Double>>> allDistancesStore = 
			Collections.synchronizedList(
				new LinkedList<List<List<Double>>>());
		
		ExecutorService pool = Executors.newFixedThreadPool(6);
		ScotlandJadeGameLogger gameLogger = ScotlandJadeHelper.createGameLogger(numDetectives, fugitive);
		List<List<Entry>> entries = gameLogger.read();
		int i = 0;
		for (List<Entry> x : entries) {
			Replay replay = new Replay(x);
			// Register own Listener to Replay
			replay.addListener(new DistanceAnalyzer(String.format("distances-%03d.csv", i++), allDistancesStore));
			pool.submit(replay);
		}
		try {
			pool.awaitTermination(2, TimeUnit.SECONDS);
			getAverageDistances(allDistancesStore);
		} catch (InterruptedException e) {
			logger.error("waiting for pool", e);
		}
		pool.shutdown();
	}

	private static void getAverageDistances(List<List<List<Double>>> allDistancesStore) {
		logger.info("calculating average values");
		
		List<List<List<Double>>> transposedDistancesStore = new ArrayList<List<List<Double>>>();
		
		for (List<List<Double>> game : allDistancesStore) {
			int roundIndex = 0;
			for (List<Double> round : game) {
				int detectiveIndex = 0;
				for (Double detectiveDistance : round) {
					if (transposedDistancesStore.size() == roundIndex) transposedDistancesStore.add(new ArrayList<List<Double>>());
					if (transposedDistancesStore.get(roundIndex).size() == detectiveIndex) transposedDistancesStore.get(roundIndex).add(new ArrayList<Double>());
					transposedDistancesStore.get(roundIndex).get(detectiveIndex).add(detectiveDistance);
					++detectiveIndex;
				}
				++roundIndex;
			}
		}
		
		List<List<Double>> averagedDistances = new LinkedList<List<Double>>();
		
		for (int roundIndex = 0; roundIndex < transposedDistancesStore.size(); ++roundIndex) {
			List<List<Double>> round = transposedDistancesStore.get(roundIndex);
			List<Double> detectiveDistances = new LinkedList<Double>();
			averagedDistances.add(detectiveDistances);
			for (int detectiveIndex = 0; detectiveIndex < round.size(); ++detectiveIndex) {
				detectiveDistances.add(getAverage(round.get(detectiveIndex)));
			}
		}
		
		writeDistances(averagedDistances, "avg.csv");
	}

	private static double getAverage(List<Double> list) {
		if (list.size() == 1) return list.get(0);
		Collections.sort(list);
		int mid = list.size() / 2;
		if (mid % 2 == 0) {
			return (list.get(mid) + list.get(mid - 1)) / 2;
		}
		return list.get(mid);
		
//		double sum = 0;
//		for (Double d : list) sum += d;
//		return sum / list.size();
	}

	private static void writeDistances(List<List<Double>> allDistances, String outputFileName) {
		File outputDir = new File(ScotlandJadeHelper.APP_DIR, "dist");
		outputDir.mkdirs();
		
		File outputFile = new File(outputDir, outputFileName);
		
		OutputStreamWriter writer = null;
		try {
			logger.info("writing output file " + outputFile);
			outputFile.createNewFile();
			writer = new OutputStreamWriter(new FileOutputStream(outputFile));
			for (List<Double> distances : allDistances) {
				StringBuilder builder = new StringBuilder();
				int remaining = distances.size();
				for (double distance : distances) {
					builder.append(distance).append(--remaining == 0 ? '\n' : ',');
				}
				writer.write(builder.toString());
			}
		} catch (IOException e) {
			logger.error("writing output file", e);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}		
	}

	private List<List<Double>> allDistances;

	private final String outputFileName;

	public DistanceAnalyzer(String fileName, List<List<List<Double>>> allDistancesStore) {
		allDistances = new LinkedList<List<Double>>();
		allDistancesStore.add(allDistances);
		outputFileName = fileName;
	}

	@Override
	public void afterReplay(Replay replay) {
//		writeDistances(allDistances, outputFileName);
	}

	@Override
	public void beforeReplay(Replay replay) {
		
	}

	@Override
	public void handleEntry(Replay replay, Entry entry) {
		// TODO Auto-generated method stub
		// get distances of detectives to mr.x
		replay.getPosition(entry.idx);
		if (entry.idx == 0) {
			ScotlandJadeVertex fugitivePosi = replay.getPosition(entry.idx);
			List<Double> detectiveDistances = new LinkedList<Double>();
			for (int i=1; i < replay.getPositions().size(); ++i) {
				ScotlandJadeVertex detectivePosi = replay.getPosition(i);
				logger.debug("" + fugitivePosi + ", " + detectivePosi);
				double shortestDist = ScotlandJadeMapHelper.info.shortestDistance(fugitivePosi, detectivePosi);
				detectiveDistances.add(shortestDist);				
			}
			allDistances.add(detectiveDistances); 
		}
	}

}
